#include "DirectorLoader.h"
#include "AIDirector.h"
#include "PlayerPerformance.h"

#include "DifficultyFactors/DifficultyFactor.h"
#include "DifficultyFactors/CharacterSpeed.h"
#include "DifficultyFactors/CharacterObstacle.h"
#include "DifficultyFactors/CatSpeed.h"
#include "DifficultyFactors/CatObstacle.h"
#include "DifficultyFactors/SpawnSets.h"

using namespace cocos2d;

//===== CONSTRUCTOR =====
AIDirector::AIDirector(int minDifficulty, int maxDifficulty, CCArray* pSpawnSets, PlayerPerformance* pPlayerPerformance)
{
	// Difficulty levels should be more than 0
	// Max must be more than min
	_minDifficulty = minDifficulty;
	_maxDifficulty = maxDifficulty;
	_currentDifficulty = ( minDifficulty + maxDifficulty )/2;
	_spawnDifficulty = 0;
	_propertyDifficulty = 0;
	_spawnPropertyDifficulty = 0;

	_period = DirectorLoader::BASE_PERIOD;
	_currentTime = 0.0;

	_pPlayerPerformance = pPlayerPerformance;
	_pSpawnSets = new SpawnSets(pSpawnSets);

	// Array of Difficulty Factors
	// Assign weights to each Factor
	// Create and call update on all factors
	initDifficultyFactors();
}

//===== DESTRUCTOR =====
AIDirector::~AIDirector(void)
{
	_pPlayerPerformance = 0;

	delete _pSpawnSets;
	_pSpawnSets = 0;

	for (int i = 0; i < NUM_OF_FACTORS; i++)
	{
		delete _pDifficultyFactors[i];
	}
}

//===== PUBLIC FUNCTIONS =====

/*!
    @function update
    @param		timeDiff: float
				time difference since the last call
    @result		updates duration of counters
    			adjust current difficulty with next difficulty levels based on PlayerPerformance
 */
void AIDirector::update(float timeDiff)
{
	_currentTime += timeDiff;

	if( canUpdate() )
	{
		// Player performance is poor, half difficulty
		if( _pPlayerPerformance->isDifficultyReduced() )
		{
			_currentTime -= DirectorLoader::BASE_PERIOD/DirectorLoader::BASE_DECREMENT; // Increases period by decreasing current time
			decreaseDifficulty( _currentDifficulty / DirectorLoader::BASE_DECREMENT );
		}
		else
		{
			int difficultyIncrement = _pPlayerPerformance->getPerformanceLevel();
			increaseDifficulty( difficultyIncrement );
		}

		updateDifficultyFactors();
		//CCLog("AI Director/Instruction (Difficulty): Difficulties=%i | Spawn=%i | Property=%i", _currentDifficulty, _spawnDifficulty, _propertyDifficulty);
	}
}

//===== PRIVATE FUNCTIONS =====

/*!
    @function canUpdate
    @result		check if Director can perform next update
 */
bool AIDirector::canUpdate()
{
	if( _currentTime < _period )
	{
		return false;
	}
	_currentTime -= _period;
	if( _currentTime < 0.0 )
	{
		_currentTime = 0.0;
	}
	return true;
}

/*!
    @function increaseDifficulty
    @result		increase currentDifficulty, check for maximum
 */
void AIDirector::increaseDifficulty(int amount)
{
	int newDifficulty = _currentDifficulty + amount;

	if( newDifficulty > _maxDifficulty )
	{
		newDifficulty = _maxDifficulty;
	}

	_currentDifficulty = newDifficulty;
}

/*!
    @function decreaseDifficulty
    @result		decrease currentDifficulty, check for minimum
 */
void AIDirector::decreaseDifficulty(int amount)
{
	int newDifficulty = _currentDifficulty - amount;

	if( newDifficulty < _minDifficulty )
	{
		newDifficulty = _minDifficulty;
	}

	_currentDifficulty = newDifficulty;
}

/*!
    @function initDifficultyFactors
    @result		add all difficulty factors into array
 */
void AIDirector::initDifficultyFactors()
{
	// Character speed, character obstacle chance
	// cats speed, cats obstacle chance
	_pWeights[0] = 0.3;
	_pWeights[1] = 0.1;
	_pWeights[2] = 0.5;
	_pWeights[3] = 0.1;

	updateDifficultyThreshold();

	_pDifficultyFactors[0] =  new CharacterSpeed(_pWeights[0], _propertyDifficulty);
	_pDifficultyFactors[1] =  new CharacterObstacle(_pWeights[1], _propertyDifficulty);
	_pDifficultyFactors[2] =  new CatSpeed(_pWeights[2], _propertyDifficulty);
	_pDifficultyFactors[3] =  new CatObstacle(_pWeights[3], _propertyDifficulty);
}

/*!
    @function updateDifficultyFactors
    @result		update request to all difficulty factors
 */
void AIDirector::updateDifficultyFactors()
{
	updateDifficultyThreshold();

	if( _propertyDifficulty <= 0 ) return;

	for (int i = 0; i < NUM_OF_FACTORS; i++)
	{
		_pDifficultyFactors[i]->update(_propertyDifficulty);
	}

	_pSpawnSets->update(_spawnDifficulty, _spawnPropertyDifficulty);
}

/*!
    @function updateDifficultyThreshold
    @result		set difficulty for reference variables passed in
 */
void AIDirector::updateDifficultyThreshold()
{
	// Difficulty must follow this order
	setSpawnDifficulty();
	setPropertyDifficulty();
	setSpawnPropertyDifficulty();
}

/*!
    @function getSpawnDifficulty
    @result		Modify difficulty assigned to cat spawn
    			Spawn must be the first difficulty to be set
 */
void AIDirector::setSpawnDifficulty()
{
	// Cat spawn must have a minimum value
	if(_currentDifficulty < DirectorLoader::CAT_SPAWN_MIN_THRESHOLD )
	{
		_spawnDifficulty = _currentDifficulty;
		return;
	}
	// After minimum, cat spawn contributes to a specific weight
	_spawnDifficulty = DirectorLoader::CAT_SPAWN_MIN_THRESHOLD + DirectorLoader::CAT_SPAWN_WEIGHT * (_currentDifficulty - DirectorLoader::CAT_SPAWN_MIN_THRESHOLD);

	// Cat spawn have a maximum value, the rest contributes to properties
	if(_spawnDifficulty > DirectorLoader::CAT_SPAWN_MAX_THRESHOLD )
	{
		_spawnDifficulty = DirectorLoader::CAT_SPAWN_MAX_THRESHOLD;
	}
}

/*!
    @function getPropertyDifficulty
    @result		modify difficulty assigned to properties
 */
void AIDirector::setPropertyDifficulty()
{
	int propertyDifficulty = _currentDifficulty - _spawnDifficulty;
	// Check for remaining difficulty after spawnDifficulty
	if( propertyDifficulty <= 0 )
	{
		_propertyDifficulty = 0;
		return;
	}

	if( propertyDifficulty > DirectorLoader::CAT_PROPERTIES_THRESHOLD)
	{
		_propertyDifficulty = DirectorLoader::CAT_PROPERTIES_THRESHOLD;
		return;
	}

	_propertyDifficulty = propertyDifficulty;
}

/*!
    @function setSpawnPropertyDifficulty
    @result		modify difficulty assigned to cat spawn property
 */
void AIDirector::setSpawnPropertyDifficulty()
{
	int spawnPropertyDifficulty = _currentDifficulty - _spawnDifficulty - _propertyDifficulty;

	if( spawnPropertyDifficulty <= 0 )
	{
		_spawnPropertyDifficulty = 0;
		return;
	}

	_spawnPropertyDifficulty = spawnPropertyDifficulty;
}

/*!
    @function getDifficultyFactorDifficulty
    @result		get difficulty for a particular difficulty factor
 */
int AIDirector::getDifficultyFactorDifficulty(int index)
{
	if( index < 0 || index >= NUM_OF_FACTORS )
		return 0;

	return _pDifficultyFactors[index]->getCurrentDifficulty();
}

